package cn.jinbyte.web.filter;


import cn.jinbyte.web.config.CsrfProperties;
import cn.jinbyte.web.utils.PathMatcherUtil;
import cn.jinbyte.web.utils.TokenUtils;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * CSRF防护过滤器，基于配置文件参数进行过滤
 */
public class CsrfFilter implements Filter {

    private final CsrfProperties csrfProperties;
    // 存储令牌创建时间，用于过期检查
    private final Map<String, Date> tokenCreationTimes = new ConcurrentHashMap<>();

    public CsrfFilter(CsrfProperties csrfProperties) {
        this.csrfProperties = csrfProperties;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        // 如果CSRF防护未启用，直接放行
        if (!csrfProperties.isEnabled()) {
            chain.doFilter(request, response);
            return;
        }

        // 检查是否为排除路径
        if (PathMatcherUtil.isExcluded(httpRequest, csrfProperties.getExcludePaths())) {
            chain.doFilter(request, response);
            return;
        }

        String method = httpRequest.getMethod();

        // 为GET请求生成CSRF令牌（如果不存在）
        if ("GET".equalsIgnoreCase(method)) {
            ensureCsrfToken(httpRequest);
            chain.doFilter(request, response);
            return;
        }

        // 检查是否需要验证CSRF令牌的方法
        List<String> validatedMethods = csrfProperties.getValidatedMethods();
        if (validatedMethods.stream().anyMatch(m -> m.equalsIgnoreCase(method))) {
            // 验证CSRF令牌
            if (!validateCsrfToken(httpRequest)) {
                httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
                httpResponse.setContentType("application/json");
                try (PrintWriter writer = httpResponse.getWriter()) {
                    writer.write("{\"code\":403,\"message\":\"CSRF令牌验证失败\"}");
                }
                return;
            }
        }

        chain.doFilter(request, response);
    }

    /**
     * 确保会话中存在有效的CSRF令牌
     */
    private void ensureCsrfToken(HttpServletRequest request) {
        HttpSession session = request.getSession();

        String sessionAttrName = csrfProperties.getSessionAttributeName();
        String token = (String) session.getAttribute(sessionAttrName);

        // 检查令牌是否存在或已过期
        if (token == null || isTokenExpired(token)) {
            token = TokenUtils.generateToken();
            session.setAttribute(sessionAttrName, token);
            tokenCreationTimes.put(token, new Date());
        }
    }

    /**
     * 验证CSRF令牌
     */
    private boolean validateCsrfToken(HttpServletRequest request) {
        HttpSession session = request.getSession(false);

        if (session == null) {
            return false;
        }

        // 获取会话中的令牌
        String sessionToken = (String) session.getAttribute(csrfProperties.getSessionAttributeName());
        if (sessionToken == null) {
            return false;
        }

        // 检查令牌是否过期
        if (isTokenExpired(sessionToken)) {
            return false;
        }

        // 从请求头或请求参数中获取令牌
        String requestToken = request.getHeader(csrfProperties.getHeaderName());
        if (requestToken == null) {
            requestToken = request.getParameter(csrfProperties.getParameterName());
        }

        // 验证令牌
        return sessionToken.equals(requestToken);
    }

    /**
     * 检查令牌是否过期
     */
    private boolean isTokenExpired(String token) {
        Date creationTime = tokenCreationTimes.get(token);
        if (creationTime == null) {
            return true;
        }

        long expirationMillis = csrfProperties.getTokenExpirationSeconds() * 1000L;
        return new Date().getTime() - creationTime.getTime() > expirationMillis;
    }
}